웹 개발자가 알아야 할 웹 지표
개발하면서 신경써야 할 점
2024-02-23
웹 사이트와 성능
사용자가 웹 사이트에 접속했을 때 공통적으로 기대하는 사항에는 뭐가 있을까?
첫번째는 웹사이트를 방문한 목적을 손쉽게 달성해야 하고, 첫번째 목적을 달성하는 데 걸리는 시간이 짧아야 하고 , 보안이 철저해야 한다. 리액트와 각종 최신 기술이 집약돼 있는 웹사이트가 내부적으로 어떤 코드로 이뤄져 있는지는 사용자 입장에서 전혀 중요한 문제가 아니다.
리액트와 각종 기술이 집약돼 있는 웹사이트라 하더라도 웹 사이트의 접근성이 떨어지고 속도가 느리거나 보인이슈가 있다면 개발자들은 좋아하는 사이트라도 사용자에게 외면을 받을 것이다.
반대로 JQuery 등의 오래된 기술로 웹사이트가 구성되어 있어도 사이트가 충분히 빠르다면 사용자가 이용하는데 전혀 지장이 없을 수 있다.
모든 서비스는 사용자가 느끼는 성능이 가장 중요하다. 웹 사이트의 성능은 다음 요소에 영향을 미쳤다.
- 웹사이트의 로딩 시간이 1초 이상 지연되면 전환율이 7% 가까이 감소할 수 있다.
- 그리고 방문자의 40%는 로딩하는데 3초 이상 걸리는 페이지는 떠날 것입니다.
- 페이지의 로드 시간이 0~2초인 페이지에서 가장 높은 전환율을 달성할 수 있다.
- 전체 페이지를 표시하는데 필요한 최적의 평균 리소스 요청 수는 50회 미만이다.
웹사이트의 고객 입장에서는 자신이 방문한 사이트가 빠르길 기대한다. 아무리 화려하고 멋진 사이트여도 그러한 화려함을 위해 시간을 희생해야 한다면 이는 무의미하다.
Core Web Vital
웹 핵심 지표(Core Web Vital)은 구글에서 만든 자료로, 웹 사이트에서 뛰어난 사용자 경험을 제공하는데 필수적인 지표를 일컫는 용어다. 과거의 웹 사이트 측정을 위한 여러 지표가 기준이 없었기 떄문에, 구글에서는 사이트에서 핵심적인 웹 지표를 몇가지로 요약하고 이를 측정할 수 있는 방법, 그리고 좋은 웹 사이트로 분류할 수 있는 기준을 명확히 제시했다.
크게 다음의 것들이 있다.
- LCP (최대 콘텐츠 풀 페인트)
- FID (최초 입력 지연)
- CLS (누적 레이아웃 이동)
그리고 다음 두 지표는 특정 문제를 판단하는데 사용될 수 있다.
- 최초 바이트까지의 시간(Time To First Byte)
- 최초 콘텐츠풀 시간(First Contentful Paint)
이러한 지표를 하나씩 알아보자!
LCP(최대 콘텐츠풀 페인트)
최대 콘텐츠풀 페인트(LCP)는 페이지가 처음으로 로드를 시작한 시점부터 뷰포트에서 가장 큰 이미지, 텍스트를 렌더링하는데 걸리는 시간을 의미한다.
사용자에게 노출되는 영역은 기기에 의존하므로 뷰포트는 상황마다 다르다. 모바일의 뷰포트는 PC에 비해 작을 것이다. 그리고 이 뷰포트 내부의 큰 이미자나 텍스트는 다음으로 정의할 수 있다.
- img태그
- svg내부의 image
- video태그
- url을 통해 배경 이미지가 등록되어 있는 요소
- 텍스트와 같이 인라인 텍스트를 포함하는 모든 요소 (p태그,div태그 등)
LCP는 사용자의 기기가 노출하는 뷰포트 내부에서 가장 큰 영역을 차지하는 요소가 렌더링되는데 얼마나 오래 걸리는지를 측정한 것이다. 실제 크기가 아무리 크다고 해도 뷰포트 영역 밖에 넘치는 요소가 있다면 해당 요소는 고려하지 않는다.
그럼 이 기준을 어떻게 잡을 수 있을까? 가장 먼저 DOMContentLoaded 이벤트를 생각할 수 있다.
이 DOMContentLoaded이벤트는 HTML문서를 완전히 불러온 후 발생하는 이벤트로 단 한번만 호출된다. 그러나 DOMContentLoaded이벤트는 스타일시트,이미지,하위 프레임의 로딩은 기다리지 않는다.
그러면 페이지가 어느정도 로딩되었다고 인지하는 시점은 언제일까? 사용자가 페이지 로딩을 체감하기 위해 페이지가 반드시 완전하게 로딩될 필요는 없다. 사용자에 있어 로딩이란 일단 뷰포트 내부를 기준으로 판단할 것이므로 뷰포트에 메인 콘텐트가 화면에 완전히 전달되는 속도를 기준으로 한다면 로딩이 완료되었다고 판단하는 시간과 유사하게 측정할 수 있다.
최대 콘텐츠풀 페인트에서 좋은 점수는 해당 지표가 2.5초 이내로 응답이 오는 것이다.
LCP의 개선
LCP를 개선하는 확실한 방법은 뷰포트 최대 영역에 이미지가 아닌 문자열을 넣는것이다. 제아무리 이미지를 최적화 하더라도, 추가적인 리소스 다운로드가 필요한 이미지보다 텍스트 노출이 훨씬 더 빠르다.
이미지를 불러오는 방법은 다음과 같은 방법이 존재한다.
<img src = "lcp.jpg" />
<svg xmlns = "https://www.w3.org/1000/svg">
<image href = "lcp.jpg">
</svg>
<video poster = "lcp.jpg"></video>
<div style = "background-image:url()"></div>
이때 불러오는 방법에는 차이가 존재한다.
-
img : 이미지는 브라우저의 프리로드 스캐너에 의해 가장 먼저 발견되어 요청을 발생시킨다. img는 HTML파싱이 완료되지 않더라도 프리로드 스캐너가 병렬적으로 리소스를 다운로드 한다. 이는 picture태그도 마찬가지이다.
-
svg 내부의 img : 모든 리소스를 불러온 이후 이미지를 불러온다. 이는 img태그와 다른 부분이다. 이는 결국 최대 콘텐츠풀 페인트 점수에 악영향을 줄 수 있다.
-
video의 poster : poster는 사용자가 vidoe요소를 재생하거나 탐색하기 전까지 노출되는 요소다. 마찬가지로 프리로드 스캐너에 발견되어 img와 같은 성능을 나타낸다.
-
background-image-url() : background-image를 비롯해 CSS에 있는 리소스는 항상 느리다. 이런 리소스는 브라우저가 해당 리소스를 필요로 하는 DOM을 그릴 때까지 리소스 요청을 뒤로 미루기 떄문이다. 따라서 최대 콘텐츠풀 페인트 점수에도 악영향을 줄 수 있다.
이미지는 또한 무손실 형식으로 압축해 최소한의 용량으로 서비스하는 것이 좋다.
만약 fadeIn ease 10s와 같이 처리했다면 이미지가 그냥 뜨는 것보다 늦어지게 되고, 최대 콘텐츠 풀 페인트도 그만큼 뒤로 늦어진다.
최대 콘텐츠풀 리소스는 같은 도메인에서 직접 호스팅 하는 것이 좋다. 일반적인 경우 Cloudinary와 같은 이미지 최적화 서비스를 통해 이미지에 대한 크기를 줄이고, 포맷도 변환하고 압축해서 이미지를 관리하지만, 다른 출처에서 이렇게 정제한 이미지를 가져오는 것은 최적화에 별로 좋은 영향을 주지 않는다. 이미 연결이 맺어진 현재 출처와는 다르게 새로운 출처의 경우 네트워크 커넥션부터 다시 수행해야 하기 떄문이다.
FID (최초 입력 지연)
수강신청이나 콘서트 표 예매 등과 같은 순간적으로 트래픽 때문에 웹사이트가 클릭이나 타이핑이 되지 않아 작업을 못한 적이 있을 것이다. 웹페이지의 로딩만큼 중요한 것이 웹 사이트의 반응 속도이고 웹사이트의 반응성을 측정하는 지표가 바로 최초 입력 지연이다.
정의는 다음과 같다.
사용자가 페이지와 처음 상호 작용할 때
해당 상호작용의 응답으로 브라우저가 실제 이벤트 핸들러를 시작하기까지의 시간
최초 입력 지연은 사용자가 얼마나 빠르게 웹패이지와 상호작용에 대한 응답을 받으르 수 있는지 측정하는 지표이다.
웹 페이지 내부의 이벤트가 반응이 늦어지는 이유는 무엇일까? 그 이유는 해당 입력을 처리해야 하는 메인 스레드가 바쁘기 떄문이다. 메인 스레드는 대규모 렌더링이 일어나거나 대규모 자바스크립트 파일을 분석하고 실행하는 등 다른 작업을 처리하는데 리소스를 할애하기 떄문에 바쁠 수 있고, 아 경우 자바스크립트가 이벤트 리스너와 같은 다른 작업을 실행할 수 없어 지연이 발생한다.
한가지 더 최초 입력 지연을 이해하기 위해 알아야 하는 것은 사용자의 입력이다. 이 최초 입력에 해당하는 내용에는 어떤 것이 있을까? 타이핑, 클릭, 스크롤 등 많지만, 최초 입력 지연에는 다양한 이벤트 중 클릭, 터치, 타이핑 등 사용자의 개발 입력 작업에 초점을 맞춘다.
최초 입력 지연의 좋은 점수를 얻기 위해서는 100ms 이내로 응답이 와야 하며, 300ms이내인 경우 보통 그 이후는 나쁨으로 처리된다.
FID 개선 방안
최초 입력 지연을 개선하려면 메인스레드에 이벤트를 실행할 여유를 줘야 한다.
긴 작업(메인 스레드에서 오래 처리되어야만 하는 작업)이 있다면 여러 작업으로 분리를 하는 것이 좋다. 작업을 분리하는 것은 실행이 오래 걸릴 것 같은 작업을 분리하는 것 뿐 아니라, 최초 로딩에 필요하지 않은 내용을 나중에 불러오는 것도 포함된다.
사용자의 액션으로 나중에 노출되는 요소들은 당장 로딩에 필요하지 않은 리소스이다. Suspense,Lazy, Next의 dynamic을 통해 나중에 불러오게 할 수 있다.
번들러가 어느정도 필요없는 자바스크립트 코드를 줄여준다 하더라도, 웹 페이지를 불러오는 데 사용되지 않는 필요 없는 코드가 존재 가능하다.이런 코드들은 앞서 언급한 지연 로딩, 우선순위를 낮추는 등의 작업으로 불러오는 것이 좋다.
Google Analytics나 firebase등과 같이 웹 페이지의 통계를 위해 타사 스크립트를 집어넣는 경우 , 메인 스레드의 사용이 잠시 점유될 수 있다. 따라서 script의 async나 defer속성을 사용하는 것이 좋다.
누적 레이아웃 이동(CLS)
웹페이지에서 로딩이 끝난 줄 알고 무언가를 클릭하려 할 때 그 사이 다른 요소가 로딩되면서 원래 클릭하려던 것이 사라지는 경험이 있을 것이다.
이처럼 페이지의 생명주기 동안 발생하는 모든 예기치 않은 이동에 대한 지표를 개산하는 것이 바로 누적 레이아웃 이동이다. 이 지표가 낮으면 낮을수록 사용자가 겪는 예상치 못한 레이아웃 이동이 적을수록 , 더 좋은 웹 사이트이다.
누적 레이아웃 이동은 가시적인 콘텐츠에 영향을 미쳐야 하기 때문에 뷰포트 내부의 요소에 대해서만 측정한다. 최초 렌더링이 시작된 위치에서 레이아웃 이동이 발생한다면 누적 레이아웃 이동 점수로 기록된다.
요소가 추가되었다 하더라도 다른 요소의 시작 위치에 영향을 미치지 않는다면 레이아웃 이동으로 간주되지 않는다. 또한 사용자의 액션으로 인해 발생한 레이아웃 이동은 점수에 포함되지 않는다.
누적 레이아웃 이동의 경우 0.1이하인 경우 좋음 0.25 이하인 경우 보통이며 그 외에는 개선이 필요한 나쁜 점수로 보고된다.
CLS 개선 방안
누적 레이아웃 이동은 클라이언트에서 삽입되는 동적인 요소로 인해 발생한다. 여기에는 갑자기 요소의 크기가 바뀌거나 뒤늦게 광고와 같은 라이브러리가 브라우저에서 로드되는 등의 작업 때문에 나타난다.
이런 작업을 방지하기 위해 useEffect 내부에서 요소에 영향을 미치는 작업을 최소화 하는 것이 좋다. 스켈레톤 UI처럼 미치 무언가 동적으로 뜰 것 같은 공간을 확보하는 것도 좋은 방법이다. 레이아웃 이동을 막이면서 클라이언트 시점에 정해지는 콘텐트를 안정적으로 보여줄 수 있으므로 추천할 만한 방법이다.
여기에 가장 좋은 방법은 서버 사이드 렌더링이다. 서버에서 이런 동적인 요소를 사전에 판단해 HTML에 제공해준다면 클라이언트는 깔끔하게 처리할 수 있다.
기존 콘텐츠 상단에 추가하는 것은 지양한다.
사용자와 상호작용을 제외하고 콘텐츠를 기존 콘텐츠의 상단에 추가해서 레이아웃이 변경되는 것은 피하는 것이 좋다. 단, 사용자 입력 후 500ms 이내에 발생하는 레이아웃 이동은 CLS에 포함되지 않는다.
-
애니메이션의 경우 transform을 사용한다. width, height를 조정하지않고 css transform 속성을 사용해 효과를 주는것이 성능에도 이득이고 사용자 경험에도 좋다.
-
웹폰트의 경우 FOIT(font of invisible text: 브라우저가 웹폰트를 다운로드하기 전에 텍스트가 보이지 않는 현상)와 FOUT(font of unstyled text: 웹폰트가 뒤늦게 로딩되면서 시스템 폰트가 갑자기 웹폰트로 바뀌는 현상)를 줄이는 것이 핵심이다.
-
비슷한 폰트 사용하기
자간, 줄간격, 폰트 자체의 사이즈 등이 달라지면서 콘텐츠가 떨어질 수 있다. 폰트가 가진 외적 특성이 비슷한 대체 글꼴 사용하면 이러한 현상을 줄일 수 있다.
/* before */
font-family: 'font-example', sans-serif;
/* after */
font-family: 'font-example', font-example 폰트를 대체할 폰트, sans-serif;
- 적절한 이미지 크기 설정
모바일 기기의 등장으로 웹 사이트가 반응형을 추구하게 되었다. 반응형 웹사이트란 사용자 기기의 크기에 따라 콘텐츠를 자연스럽게 노출할 수 있도록 다양한 요소를 콘텐츠의 기기에 의존하게 하는 것이다.
img {
width:100%;
height:auto;
}
이 경우 누적 레이아웃 이동이 커지는 결과를 낳는다. 높이를 이미지가 완전히 다운로드 될때까지 알 수 없기 떄문에 이미지의 높이를 높게 잡아놓았다가 이미지가 완전히 로딩 완료 후에 너비만큼 높이를 계산해서 마침내 이미지 크기만큼 자리잡게 된다.
이를 해결하기 위해 명시적으로 width,height속성을 지정하거나 사용자 뷰포트 너비으 맞춰 다른 이미지를 제공하고 싶다면 srcset속성을 사용하는 것이 좋다.
무엇보다 뷰포트는 사용자에게 첫번쨰로 웹 페이지에 대한 인상을 주는 중요한 영역이므로 동적 컨텐츠를 신중히 고민해야 한다.
TTFB와 FCP
최초 바이트까지의 시간(Time to First Byte)는 브라우저가 웹 페이지의 첫번쨰 바이트를 수신하는데 걸리는 시간을 의미한다. 즉 페이지를 요청했을 때 요청이 완전히 완료되는데 걸리는 시간을 측정한 것이 아니라, 최초로 응답이 오는 바이트까지 얼마나 걸리는지를 측정하는 지표다.
이 지표는 600ms이상의 경우 개선이 필요한 것으로 판단된다.
이는 서버 사이드 렌더링을 하고 있는 웹에서 주의 깊게 살펴보아야 한다. 이는 일반적인 싱글 페이지 애플리케이션과 달리 서버 사이드 렌더링은 최초 페이지를 만들기 위해 서버에서 어느정도 작업을 수행해야 하기 때문이다. 서버에서 첫번째 HTML을 만들기 위해 해야 하는 작업이 많거나 느릴수록 최초 바이트까지의 시간이 길어지게 된다.
이를 개선하려면 다음을 고려해야 한다.
- 서버 사이드 렌더링의 경우 로직을 최적화 해 페이지를 빨리 준비해야 한다.
- 응답해야 할 서버가 사용자에 가까울수록 응답속도가 빨라진다. 최대한 해당 국적과 가깝게 서버를 위치시킨다.
- 스트리밍 API를 사용하면 시간을 단축할 수 있다.
FCP(First Contentful Paint)는 페이지가 로드되기 시작한 시점부터 페이지 콘텐츠의 일부가 화면에 렌더링될때까지의 시간을 말한다.
즉, 웹에 접속한 순간부터 페이지에 뭐라도 뜨기 시작한 시점까지의 시간을 말한다.